JSON Data in Flutter
JSON, or JavaScript Object Notation, is one of the most common formats used to exchange structured data between Flutter applications and backend servers. Flutter applications frequently receive JSON from REST APIs and convert that JSON into Dart objects. Flutter also uses JSON serialization when application data needs to be converted into a format suitable for transmission or storage.
Flutter applications can work with JSON using Dart's built-in dart:convert library. For larger applications, model classes and code-generation approaches such as json_serializable can help organize serialization and deserialization.
1. What is JSON?
JSON stands for JavaScript Object Notation. It is a lightweight text-based format used to represent structured data.
A simple JSON object looks like this:
{
"name": "Rahul",
"age": 25,
"email": "[email protected]"
}
JSON is commonly used when a Flutter application communicates with a REST API.
Important Characteristics of JSON
- JSON is text-based.
- JSON represents structured data.
- JSON is easy for humans to read.
- JSON is easy for programs to parse.
- JSON is commonly used in REST APIs.
- JSON can represent objects, arrays, strings, numbers, booleans, and null values.
2. Why is JSON Used in Flutter?
JSON is commonly used in Flutter applications for communicating with backend services and representing structured information.
- Fetching data from REST APIs.
- Sending data to backend servers.
- Representing users and products.
- Handling authentication responses.
- Storing structured configuration data.
- Exchanging data between applications and services.
- Converting API responses into Dart objects.
A typical Flutter application receives JSON from an API, decodes it, converts it into Dart models, and then uses those models to display information in the UI.
3. JSON Data Types
JSON supports several basic data types.
| JSON Type |
Example |
Dart Equivalent |
| String |
"Rahul" |
String |
| Number |
25 |
int / double |
| Boolean |
true |
bool |
| Object |
{"name":"Rahul"} |
Map |
| Array |
["Flutter","Dart"] |
List |
| Null |
null |
Nullable Dart value |
4. JSON Object
A JSON object contains key-value pairs inside curly braces {}.
{
"id": 1,
"name": "Rahul",
"email": "[email protected]"
}
Each key is normally a string and is associated with a value.
JSON Object Structure
{
"key": "value"
}
In Flutter, a decoded JSON object is commonly represented as a Dart Map.
5. JSON Array
A JSON array contains multiple values inside square brackets [].
[
"Flutter",
"Dart",
"Firebase"
]
A JSON API can also return an array of objects:
[
{
"id": 1,
"name": "Rahul"
},
{
"id": 2,
"name": "Priya"
}
]
In Dart, a JSON array is commonly represented as a List.
6. Nested JSON Objects
JSON objects can contain other objects.
{
"id": 1,
"name": "Rahul",
"address": {
"city": "Mumbai",
"country": "India"
}
}
Nested JSON structures are common in real-world APIs and usually require nested Dart model classes for clean handling.
Nested JSON Flow
JSON Object
↓
User Object
↓
Address Object
↓
Address Model
7. JSON Arrays Inside Objects
An object can contain an array as one of its properties.
{
"id": 1,
"name": "Rahul",
"skills": [
"Dart",
"Flutter",
"Firebase"
]
}
In Dart, the skills field can be represented as a List.
final skills = json['skills'] as List;
8. JSON and Dart
Dart provides the dart:convert library for working with JSON.
import 'dart:convert';
The library provides functions such as:
jsonDecode() – converts JSON text into Dart data.
jsonEncode() – converts Dart data into JSON text.
The basic relationship is:
JSON String
↓
jsonDecode()
↓
Dart Map / List
Dart Map / List
↓
jsonEncode()
↓
JSON String
9. What is JSON Decoding?
Decoding means converting a JSON string received from an API or another source into Dart data structures.
JSON String
↓
jsonDecode()
↓
Map / List
↓
Dart Model
↓
Flutter UI
Decoding is mainly required when the application receives JSON data from a server.
10. Using jsonDecode()
The jsonDecode() function converts a JSON string into a Dart object such as a Map or List.
import 'dart:convert';
void main() {
const jsonString = '''
{
"name": "Rahul",
"age": 25
}
''';
final data = jsonDecode(jsonString);
print(data['name']);
print(data['age']);
}
When the JSON contains an object, the decoded result can be accessed using keys.
11. Decoding JSON into a Map
When a JSON object is returned, it can be represented as a Map.
final data = jsonDecode(jsonString) as Map;
print(data['name']);
print(data['age']);
Using an explicit map type provides clearer expectations about the structure of the decoded data.
Typed Map Example
final Map<String, dynamic> data =
jsonDecode(jsonString) as Map<String, dynamic>;
12. Accessing JSON Values
final data = jsonDecode(jsonString) as Map<String, dynamic>;
final String name = data['name'] as String;
final int age = data['age'] as int;
print(name);
print(age);
For data coming from an external API, values should be validated or cast carefully because the server response may not always match the expected structure.
13. Decoding a JSON Array
If an API returns an array, jsonDecode() can produce a Dart List.
const jsonString = '''
[
{
"id": 1,
"name": "Rahul"
},
{
"id": 2,
"name": "Priya"
}
]
''';
final data = jsonDecode(jsonString) as List;
print(data[0]['name']);
print(data[1]['name']);
When the response contains multiple records, each element of the list can later be converted into a Dart model.
14. What is JSON Encoding?
Encoding is the process of converting Dart data into a JSON string.
Dart Object / Map
↓
jsonEncode()
↓
JSON String
↓
API Request
Encoding is commonly required when sending data to a backend API.
15. Using jsonEncode()
import 'dart:convert';
void main() {
final user = {
'name': 'Rahul',
'age': 25,
'email': '[email protected]',
};
final jsonString = jsonEncode(user);
print(jsonString);
}
The resulting JSON represents the Dart map as JSON text.
16. Encoding a List
import 'dart:convert';
void main() {
final skills = [
'Dart',
'Flutter',
'Firebase',
];
final jsonString = jsonEncode(skills);
print(jsonString);
}
The result is a JSON array.
17. JSON Encoding for POST Requests
When sending JSON data to an API, the Dart data can be encoded using jsonEncode().
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> createUser() async {
final response = await http.post(
Uri.parse('https://example.com/api/users'),
headers: {
'Content-Type': 'application/json',
},
body: jsonEncode({
'name': 'Rahul',
'email': '[email protected]',
}),
);
print(response.statusCode);
}
The general sequence is:
Dart Map
↓
jsonEncode()
↓
JSON String
↓
HTTP POST
↓
Server
18. JSON with HTTP GET Requests
GET requests frequently receive JSON responses from APIs.
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> fetchUser() async {
final response = await http.get(
Uri.parse('https://example.com/api/users/1'),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body) as Map<String, dynamic>;
print(data['name']);
}
}
The typical process is:
HTTP GET
↓
HTTP Response
↓
response.body
↓
jsonDecode()
↓
Map / List
↓
Dart Model
19. Creating a Dart Model Class
A model class represents the structure of application data.
class User {
final String name;
final String email;
User({
required this.name,
required this.email,
});
}
A model provides a clear structure for data instead of accessing JSON map values throughout the application.
Why Use Models?
- Improves readability.
- Provides a predictable data structure.
- Improves type safety.
- Centralizes JSON conversion logic.
- Makes application code easier to maintain.
20. Using fromJson()
A fromJson() factory constructor can convert a JSON map into a Dart object.
class User {
final String name;
final String email;
User({
required this.name,
required this.email,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
name: json['name'] as String,
email: json['email'] as String,
);
}
}
The basic flow is:
JSON Map
↓
User.fromJson()
↓
User Object
21. Converting JSON into a Dart Object
import 'dart:convert';
void main() {
const jsonString = '''
{
"name": "Rahul",
"email": "[email protected]"
}
''';
final userMap =
jsonDecode(jsonString) as Map<String, dynamic>;
final user = User.fromJson(userMap);
print(user.name);
print(user.email);
}
The JSON map is first decoded and then passed to User.fromJson().
22. Using toJson()
The toJson() method performs the reverse operation. It converts a Dart model object into a map that can be encoded as JSON.
class User {
final String name;
final String email;
User({
required this.name,
required this.email,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
name: json['name'] as String,
email: json['email'] as String,
);
}
Map<String, dynamic> toJson() {
return {
'name': name,
'email': email,
};
}
}
23. Encoding a Dart Model
import 'dart:convert';
final user = User(
name: 'Rahul',
email: '[email protected]',
);
final jsonString = jsonEncode(user.toJson());
print(jsonString);
The serialization flow is:
User Object
↓
toJson()
↓
Map
↓
jsonEncode()
↓
JSON String
24. Complete fromJson() and toJson() Example
class Product {
final int id;
final String name;
final double price;
Product({
required this.id,
required this.name,
required this.price,
});
factory Product.fromJson(Map<String, dynamic> json) {
return Product(
id: json['id'] as int,
name: json['name'] as String,
price: (json['price'] as num).toDouble(),
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'price': price,
};
}
}
This pattern can be reused for most API model classes.
25. JSON List to List of Models
When an API returns multiple objects, each JSON object can be converted into a model.
final List<dynamic> data =
jsonDecode(response.body) as List;
final users = data
.map(
(item) => User.fromJson(
item as Map<String, dynamic>,
),
)
.toList();
The result is a list of User objects.
26. Strongly Typed JSON Lists
You can explicitly declare the resulting model list.
final List<User> users =
(jsonDecode(response.body) as List)
.map(
(item) => User.fromJson(
item as Map<String, dynamic>,
),
)
.toList();
This makes the expected data type clear to the rest of the application.
Conversion Flow
JSON Array
↓
List
↓
Map for each item
↓
User.fromJson()
↓
List<User>
27. Nested JSON and Model Classes
Consider the following JSON:
{
"name": "Rahul",
"address": {
"city": "Mumbai",
"country": "India"
}
}
Create a separate model for the nested object.
class Address {
final String city;
final String country;
Address({
required this.city,
required this.country,
});
factory Address.fromJson(Map<String, dynamic> json) {
return Address(
city: json['city'] as String,
country: json['country'] as String,
);
}
Map<String, dynamic> toJson() {
return {
'city': city,
'country': country,
};
}
}
Then use it inside the User model:
class User {
final String name;
final Address address;
User({
required this.name,
required this.address,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
name: json['name'] as String,
address: Address.fromJson(
json['address'] as Map<String, dynamic>,
),
);
}
Map<String, dynamic> toJson() {
return {
'name': name,
'address': address.toJson(),
};
}
}
Nested Model Flow
JSON
↓
User.fromJson()
↓
Address.fromJson()
↓
Address Object
↓
User Object
28. JSON with Null Values
API responses can contain null values.
{
"name": "Rahul",
"phone": null
}
If a field can be missing or null, model it as nullable when appropriate.
class User {
final String name;
final String? phone;
User({
required this.name,
this.phone,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
name: json['name'] as String,
phone: json['phone'] as String?,
);
}
}
The ? indicates that the Dart value may be null.
29. Handling Optional JSON Fields
Some APIs may not always return every field.
{
"name": "Rahul"
}
When a field may be absent, the model should account for that possibility.
class User {
final String name;
final String? phone;
User({
required this.name,
this.phone,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
name: json['name'] as String,
phone: json['phone'] as String?,
);
}
}
Nullable fields help prevent assumptions that a value will always be present.
30. JSON Type Conversion
JSON numbers may need careful handling when converting them to Dart numeric types.
For example, when an API may return an integer or decimal number, num can be converted to double.
final price = (json['price'] as num).toDouble();
This is useful when the API's numeric representation may vary between whole and decimal values.
31. JSON Boolean Values
JSON supports boolean values using true and false.
{
"name": "Flutter",
"isActive": true
}
Dart model:
class Course {
final String name;
final bool isActive;
Course({
required this.name,
required this.isActive,
});
factory Course.fromJson(Map<String, dynamic> json) {
return Course(
name: json['name'] as String,
isActive: json['isActive'] as bool,
);
}
}
32. JSON with HTTP Response
JSON is frequently found inside the body of an HTTP response.
final response = await http.get(
Uri.parse('https://example.com/api/users'),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
print(data);
}
The typical Flutter networking flow is to make the HTTP request, check the response status code, decode the response body, and convert the resulting data into a Dart model.
33. JSON Response Error Handling
JSON parsing should generally happen only after confirming that the HTTP request returned an appropriate response.
Future<User> fetchUser() async {
final response = await http.get(
Uri.parse('https://example.com/api/users/1'),
);
if (response.statusCode == 200) {
final data =
jsonDecode(response.body) as Map<String, dynamic>;
return User.fromJson(data);
}
throw Exception(
'Failed to load user: ${response.statusCode}',
);
}
Checking the status code before parsing helps separate successful responses from HTTP errors.
34. JSON FormatException
If the server returns malformed JSON, decoding can fail.
try {
final data = jsonDecode(response.body);
print(data);
} on FormatException catch (e) {
print('Invalid JSON: $e');
} catch (e) {
print('Unexpected error: $e');
}
This helps prevent malformed response data from causing an unhandled application error.
35. Manual JSON Serialization
For smaller projects, JSON serialization can be implemented manually using fromJson() and toJson().
class User {
final String name;
final String email;
User({
required this.name,
required this.email,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
name: json['name'] as String,
email: json['email'] as String,
);
}
Map<String, dynamic> toJson() {
return {
'name': name,
'email': email,
};
}
}
Manual serialization is straightforward and is often useful when an application contains only a small number of models.
36. Advantages of Model-Based JSON Parsing
- Provides a clear data structure.
- Improves code readability.
- Provides better type safety.
- Provides better autocomplete.
- Reduces repeated JSON field access.
- Keeps parsing logic inside the model.
- Makes API data easier to use throughout the application.
Raw Map vs Model
| Approach |
Characteristics |
| Raw JSON Map |
Quick but less structured |
| Dart Model |
Structured and easier to maintain |
| Generated Model |
Reduces repetitive serialization code |
37. JSON Serialization with Code Generation
For medium and large applications with many models, manually writing serialization code can become repetitive. Code generation provides an alternative approach.
One commonly used solution is json_serializable.
Required Packages
dependencies:
json_annotation: <current-version>
dev_dependencies:
build_runner: <current-version>
json_serializable: <current-version>
Use the appropriate current package versions for your project rather than copying a fixed version number from an old example.
38. Using json_serializable
A model can be annotated so that serialization code is generated.
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
@JsonSerializable()
class User {
User(this.name, this.email);
String name;
String email;
factory User.fromJson(
Map<String, dynamic> json,
) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
The generated file contains the serialization boilerplate.
How It Works
User Model
↓
@JsonSerializable()
↓
build_runner
↓
Generated user.g.dart
↓
fromJson() / toJson()
39. Generating JSON Serialization Code
After configuring json_serializable, the Dart build runner can generate the required code.
dart run build_runner build --delete-conflicting-outputs
For continuous generation while developing:
dart run build_runner watch --delete-conflicting-outputs
The watcher can automatically regenerate serialization code when model files change.
40. Manual Serialization vs Code Generation
| Feature |
Manual Serialization |
Code Generation |
| Setup |
Simple |
Requires configuration |
| Dependencies |
Uses dart:convert |
Uses generation packages |
| Small projects |
Suitable |
May be unnecessary |
| Large projects |
Can become repetitive |
Scales well |
| Boilerplate |
Written manually |
Generated |
| Maintenance |
Manual |
Generated |
The appropriate approach depends on the size, number of models, and complexity of the application.
41. JSON Field Naming
APIs sometimes use naming conventions such as snake_case.
Example JSON:
{
"first_name": "Rahul",
"last_name": "Sharma"
}
Your Dart model may use camelCase:
final String firstName;
final String lastName;
When using json_serializable, annotations such as @JsonKey can map JSON field names to Dart properties.
Example
@JsonKey(name: 'first_name')
final String firstName;
42. JSON Field Rename with json_serializable
For a model with multiple snake_case fields, json_serializable can also use a field renaming strategy.
@JsonSerializable(
fieldRename: FieldRename.snake,
)
class User {
User(this.firstName, this.lastName);
final String firstName;
final String lastName;
factory User.fromJson(
Map<String, dynamic> json,
) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
This can automatically map Dart camelCase fields to JSON snake_case fields.
43. JSON Default Values
Sometimes an API may omit a field or return null. Code-generation annotations can provide default values for selected fields.
@JsonKey(defaultValue: false)
final bool isActive;
Default values can be useful when an application expects a value even when the API does not provide one.
44. Validating JSON Data
External API data should not automatically be assumed to have the expected structure.
For example, a model can validate the types of important fields:
factory User.fromJson(Map<String, dynamic> json) {
if (json['name'] is! String ||
json['email'] is! String) {
throw const FormatException(
'Invalid user JSON',
);
}
return User(
name: json['name'] as String,
email: json['email'] as String,
);
}
Validation can help identify unexpected API responses earlier in the data-processing flow.
45. JSON and API Architecture
In a larger Flutter application, JSON parsing can be separated into different layers.
Flutter UI
↓
ViewModel / State Management
↓
Repository
↓
API Service
↓
HTTP Request
↓
JSON Response
↓
jsonDecode()
↓
Dart Model
↓
Application State
↓
Flutter UI
This structure keeps API communication and JSON parsing separate from presentation code.
Responsibilities of Each Layer
| Layer |
Responsibility |
| UI |
Displays application data |
| ViewModel / State |
Manages UI state and actions |
| Repository |
Coordinates data sources |
| API Service |
Communicates with backend services |
| Model |
Represents structured data |
| JSON Parser |
Converts JSON and Dart data |
46. Complete JSON API Example
import 'dart:convert';
import 'package:http/http.dart' as http;
class User {
final int id;
final String name;
final String email;
User({
required this.id,
required this.name,
required this.email,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as int,
name: json['name'] as String,
email: json['email'] as String,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'email': email,
};
}
}
Future<User> fetchUser() async {
final response = await http.get(
Uri.parse(
'https://example.com/api/users/1',
),
headers: {
'Accept': 'application/json',
},
);
if (response.statusCode == 200) {
final json =
jsonDecode(response.body) as Map<String, dynamic>;
return User.fromJson(json);
}
throw Exception('Failed to load user');
}
Complete Flow
Flutter
↓
fetchUser()
↓
HTTP GET
↓
Server
↓
JSON Response
↓
jsonDecode()
↓
User.fromJson()
↓
User Object
↓
Flutter UI
47. JSON with POST Request
The following example demonstrates converting a Dart map into JSON before sending it to a server.
Future<User> createUser(
String name,
String email,
) async {
final response = await http.post(
Uri.parse(
'https://example.com/api/users',
),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode({
'name': name,
'email': email,
}),
);
if (response.statusCode == 201) {
final json =
jsonDecode(response.body) as Map<String, dynamic>;
return User.fromJson(json);
}
throw Exception('Failed to create user');
}
The complete sequence is:
Dart Data
↓
jsonEncode()
↓
JSON Request Body
↓
HTTP POST
↓
Server
↓
JSON Response
↓
jsonDecode()
↓
User.fromJson()
↓
User Object
48. JSON Data in FutureBuilder
JSON-based API data can be displayed using FutureBuilder.
Future<User> fetchUser() async {
final response = await http.get(
Uri.parse(
'https://example.com/api/users/1',
),
);
if (response.statusCode == 200) {
return User.fromJson(
jsonDecode(response.body)
as Map<String, dynamic>,
);
}
throw Exception('Failed to load user');
}
UI example:
FutureBuilder<User>(
future: fetchUser(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return const CircularProgressIndicator();
}
if (snapshot.hasError) {
return Text(
'Error: ${snapshot.error}',
);
}
if (snapshot.hasData) {
final user = snapshot.data!;
return Column(
children: [
Text(user.name),
Text(user.email),
],
);
}
return const Text('No user found');
},
)
FutureBuilder Data Flow
Future
↓
Waiting
↓
HTTP Request
↓
JSON Response
↓
Dart Model
↓
FutureBuilder
↓
Flutter UI
49. Parsing Large JSON Data
Large JSON responses can require significant processing. When JSON parsing becomes expensive enough to affect UI responsiveness, parsing work can be moved away from the main UI isolate.
Typical Architecture
HTTP Response
↓
Large JSON String
↓
Background Isolate
↓
JSON Parsing
↓
Dart Models
↓
Main Flutter UI
For normal-sized API responses, regular decoding may be sufficient. Background parsing is mainly relevant when the amount of data or parsing work is large enough to cause noticeable UI work.
50. JSON Best Practices
- Use
dart:convert for basic JSON encoding and decoding.
- Use
jsonDecode() for decoding JSON strings.
- Use
jsonEncode() for encoding Dart data.
- Use model classes for structured application data.
- Keep
fromJson() and toJson() logic organized.
- Validate external data when appropriate.
- Handle nullable fields correctly.
- Check HTTP status codes before decoding successful API responses.
- Use code generation for larger projects when manual serialization becomes repetitive.
- Keep JSON parsing separate from UI code.
- Use background parsing for sufficiently large JSON payloads when needed.
- Write tests for important serialization and deserialization logic.
51. Common JSON Mistakes
- Forgetting to import
dart:convert.
- Using
jsonEncode() when you actually need jsonDecode().
- Using
jsonDecode() when you need to send JSON.
- Assuming every API field is always present.
- Ignoring nullable fields.
- Assuming an API number is always an
int when it may be decimal.
- Accessing deeply nested JSON without checking its structure.
- Putting raw JSON map access throughout the UI.
- Not checking the HTTP response status code.
- Not handling malformed JSON.
- Manually writing large amounts of repetitive serialization code.
Common Mistake: Confusing Encode and Decode
| Function |
Direction |
Typical Usage |
jsonDecode() |
JSON → Dart |
Reading API responses |
jsonEncode() |
Dart → JSON |
Sending API requests |
52. JSON Serialization Flow
Dart Object
↓
toJson()
↓
Map
↓
jsonEncode()
↓
JSON String
↓
HTTP Request
↓
Server
53. JSON Deserialization Flow
Server
↓
JSON Response
↓
HTTP Response Body
↓
jsonDecode()
↓
Map / List
↓
fromJson()
↓
Dart Model
↓
Flutter UI
54. JSON Quick Revision
| Concept |
Purpose |
| JSON |
Structured data exchange format |
dart:convert |
Dart library for JSON conversion |
jsonDecode() |
JSON string to Dart data |
jsonEncode() |
Dart data to JSON string |
fromJson() |
JSON map to Dart model |
toJson() |
Dart model to JSON-compatible map |
json_serializable |
Code generation for JSON serialization |
| Model class |
Represents structured application data |
55. Practice Exercise
Create a Flutter application that retrieves user data from an API and displays it on the screen.
Requirements
- Create a
User model.
- Add
fromJson().
- Add
toJson().
- Send a GET request.
- Decode the JSON response.
- Convert JSON into a User object.
- Display the user's name and email.
- Show a loading indicator while data is being fetched.
- Show an error message if JSON parsing or the network request fails.
Advanced Exercise
- Create a list of users.
- Parse a JSON array into
List.
- Add a POST form for creating a user.
- Convert the user model to JSON.
- Send the JSON using
http.post().
- Parse the returned JSON response.
Mini Project Flow
User Screen
↓
Fetch Button
↓
HTTP GET
↓
JSON Response
↓
jsonDecode()
↓
User.fromJson()
↓
User Object
↓
Display User
56. Interview Questions
Q1. What is JSON?
JSON is a lightweight text-based format commonly used to represent and exchange structured data.
Q2. Which Dart library is used for JSON encoding and decoding?
The built-in dart:convert library provides JSON encoding and decoding functionality.
Q3. What does jsonDecode() do?
jsonDecode() converts a JSON string into Dart data such as a Map or List.
Q4. What does jsonEncode() do?
jsonEncode() converts Dart data into a JSON string.
Q5. What is fromJson()?
fromJson() is commonly implemented as a factory constructor that creates a Dart model object from a JSON-compatible map.
Q6. What is toJson()?
toJson() converts a Dart model object into a JSON-compatible map.
Q7. Why use model classes instead of raw JSON maps?
Model classes provide clearer structure, stronger typing, better autocomplete, and centralized serialization logic.
Q8. When should code generation be considered?
Code generation can be useful when an application has many JSON models and manually maintaining serialization code becomes repetitive.
Q9. What is the difference between serialization and deserialization?
Serialization converts Dart data into a JSON-compatible representation, while deserialization converts JSON data into Dart objects.
Q10. What is the difference between jsonEncode() and jsonDecode()?
| Function |
Input |
Output |
jsonEncode() |
Dart data |
JSON string |
jsonDecode() |
JSON string |
Dart data |
57. Recommended JSON Development Process
- Understand the API response structure.
- Create Dart model classes.
- Implement
fromJson().
- Implement
toJson() when data must be sent back to the server.
- Make nullable fields nullable in Dart when appropriate.
- Perform HTTP requests through a service or repository layer.
- Check HTTP status codes.
- Decode the JSON response.
- Convert JSON maps into model objects.
- Expose model data to the application state.
- Display the model data in Flutter widgets.
- Handle loading, empty, parsing, and network error states.
- Use code generation when the number of models makes manual serialization repetitive.
Development Flow
API Documentation
↓
JSON Structure
↓
Dart Model
↓
fromJson()
↓
API Service
↓
Repository
↓
State Management
↓
Flutter UI
58. Official Flutter JSON Resources
59. Learn Flutter with JustAcademy
For structured Flutter training and practical learning, explore the following resources:
JustAcademy Flutter Training Course
Register for Flutter Course Demo
60. Learning Outcome
After studying this topic, you should be able to:
- Understand the purpose and structure of JSON.
- Identify common JSON data types.
- Decode JSON using
jsonDecode().
- Encode Dart data using
jsonEncode().
- Work with JSON objects and arrays.
- Create Dart model classes for API responses.
- Implement
fromJson() and toJson().
- Handle nested JSON structures.
- Handle nullable and optional JSON fields.
- Convert JSON arrays into lists of Dart models.
- Work with JSON in HTTP GET and POST requests.
- Handle JSON parsing and HTTP errors.
- Use
json_serializable for generated serialization code.
- Organize JSON processing into a maintainable application architecture.
61. Summary
JSON is an important part of Flutter application development because it provides a common structure for exchanging data between Flutter applications and backend services. The dart:convert library provides jsonDecode() for converting JSON strings into Dart data and jsonEncode() for converting Dart data into JSON strings.
For simple applications, manual serialization using fromJson() and toJson() is straightforward. For larger applications with many models, code generation with tools such as json_serializable can reduce repetitive serialization code.
The overall JSON workflow can be remembered as:
API Response
↓
JSON String
↓
jsonDecode()
↓
Map / List
↓
fromJson()
↓
Dart Model
↓
State Management
↓
Flutter UI
For sending data:
Flutter Data
↓
Dart Model
↓
toJson()
↓
Map
↓
jsonEncode()
↓
JSON String
↓
HTTP Request
↓
Backend Server